<?php

/**
 * @see Base
 */
require_once LIB_PATH . 'Base.php';

/**
 * @see Project
 */
require_once LIB_PATH . 'Project.php';

/**
 * ProjectFactory 
 *
 * @uses Base
 * @package php-phoenix
 * @subpackage project
 * @version $id$
 * @author Pablo López Torres <pablolopeztorres@gmail.com> 
 */
class ProjectFactory extends Base
{
    /**
     * PHOENIX_CONFIG_KEY 
     * 
     * @const string
     */
    const PHOENIX_CONFIG_KEY = 'phoenix_config';

    /**
     * projects 
     * 
     * @static
     * @var array
     * @access private
     */
    private static $projects = array();


    //
    // Config related
    //
    /**
     * config 
     * 
     * @var array
     * @access private
     */
    private $config;

    /**
     * createConfig
     * 
     * @access private
     * @author Pablo López Torres <pablolopeztorres@gmail.com> 
     * @return void
     */
    private function createConfig()
    {
        return array(
            'projects' => array(),
			'default_project' => NULL,
        );
    }

    /**
     * saveConfig
     * 
     * @access private
     * @author Pablo López Torres <pablolopeztorres@gmail.com> 
     * @return void
     */
    private function saveConfig()
    {
        return Storage::save($this->config, self::PHOENIX_CONFIG_KEY, TRUE);
    }

    /**
     * loadConfig
     * 
     * @access private
     * @author Pablo López Torres <pablolopeztorres@gmail.com> 
     * @return void
     */
    private function loadConfig()
    {
        $this->config = Storage::load(self::PHOENIX_CONFIG_KEY);

        if ($this->config === NULL)
        {
            $this->config = $this->createConfig();
            $this->saveConfig();
        }
    }

    /**
     * setConfig
     * 
     * @access private
     * @author Pablo López Torres <pablolopeztorres@gmail.com> 
     * @param mixed $key 
     * @param mixed $value 
     * @return void
     */
    private function setConfig($key, $value)
    {
        if ($this->config === NULL)
        {
            $this->loadConfig();
        }

        $this->config[$key] = $value;

        $this->saveConfig();
    }

    /**
     * getConfig
     * 
     * @access private
     * @author Pablo López Torres <pablolopeztorres@gmail.com> 
     * @param mixed $key 
     * @return void
     */
    private function getConfig($key)
    {
        if ($this->config === NULL)
        {
            $this->loadConfig();
        }

        $result = NULL;
        if (isset($this->config[$key]))
        {
            $result = $this->config[$key];
        }
        return $result;
    }


    //
    // Singleton pattern
    //
    /**
     * __construct
     * 
     * @access private
     * @author Pablo López Torres <pablolopeztorres@gmail.com> 
     * @return void
     */
    private function __construct()
    {
        // just to assure it is not instanciated from the outside 
    }

    /**
     * instance 
     * 
     * @static
     * @var mixed
     * @access private
     */
    private static $instance;

    /**
     * getInstance
     * 
     * @static
     * @access public
     * @author Pablo López Torres <pablolopeztorres@gmail.com> 
     * @return void
     */
    public static function getInstance()
    {
        if (! isset(self::$instance))
        {
            self::$instance = new self();
        }

        return self::$instance;
    }


    //
    // Crud
    //
    /**
     * create
     * 
     * @access public
     * @author Pablo López Torres <pablolopeztorres@gmail.com> 
     * @param mixed $projectName 
     * @param mixed $projectPath 
     * @return void
     */
    public function create($projectName, $projectPath)
    {
        if (! is_dir($projectPath))
        {
            throw new DirectoryDoesNotExistException($projectPath);
        }

        $project = new Project();
        $project->setName($projectName);
        $project->setPath($projectPath);
        $project->identify($projectName);

        self::$projects[$projectName] = $project;

        if (! $this->save($projectName))
        {
            throw new ProjectAlreadyExistsException();
        }

        // update the configuration with this project
        $config = $this->getConfig('projects');
		$config[$projectName] = TRUE;
        $this->setConfig('projects', $config);

        return $project;
    }

    /**
     * delete
     * 
     * @access public
     * @author Pablo López Torres <pablolopeztorres@gmail.com> 
     * @param mixed $projectName 
     * @param mixed $projectPath 
     * @return void
     */
    public function delete($projectName)
    {
		$persistentKey = Project::getPersistentKey($projectName);

		$wasDeleted = Storage::delete($persistentKey);
        unset(self::$projects[$projectName]);

        // update the configuration with this project
        $config = $this->getConfig('projects');
		unset($config[$projectName]);
        $this->setConfig('projects', $config);

        $config = $this->getConfig('default_project');
		if ($config === $projectName)
		{
			$this->setConfig('default_project', NULL);
		}

		return $wasDeleted;
    }

    /**
     * load
     * 
     * @access public
     * @author Pablo López Torres <pablolopeztorres@gmail.com> 
     * @param string $projectName 
     * @return Project
     */
    public function load($projectName)
    {
		if ($projectName === NULL)
		{
			return NULL;
		}

        if (! isset(self::$projects[$projectName]))
        {
            $persistentKey = Project::getPersistentKey($projectName);
            $project = Storage::load($persistentKey);
            $config = $this->getConfig('projects');

            if ($project === NULL)
            {
                if (in_array($projectName, $config))
                {
                    $config = $this->getConfig('projects');
                    unset($config[$projectName]);
                    $this->setConfig('projects', $config);
                }
                throw new ProjectNotFoundException($projectName);
            }

            if (! $project instanceof Project)
            {
                throw new ProjectCorruptedException();
            }

            // update the configuration with this project
            if (! in_array($projectName, $config))
            {
                $config[$projectName] = TRUE;
                $this->setConfig('projects', $config);
            }

            self::$projects[$projectName] = $project;
        }

        return self::$projects[$projectName];
    }

    /**
     * save
     * 
     * @access public
     * @author Pablo López Torres <pablolopeztorres@gmail.com> 
     * @param string $projectName 
     * @param boolean $forceOverwrite 
     * @return boolean
     */
    public function save($projectName, $forceOverwrite = NULL)
    {
        if ($forceOverwrite === NULL)
        {
            $forceOverwrite = FALSE;
        }

        if (! isset(self::$projects[$projectName]))
        {
            throw new ProjectNotLoadedException($projectName);
        }

        $project = self::$projects[$projectName];
        $projectIdentifier = $project->getIdentifier();
        if ($projectIdentifier === NULL)
        {
            throw new ElementNotIdentifiedException();
        }

        $persistentKey = $project->getPersistentKey($projectIdentifier);
        return Storage::save($project, $persistentKey, $forceOverwrite);

    }

    /**
     * exists
     * 
     * @access public
     * @author Pablo López Torres <pablolopeztorres@gmail.com> 
     * @param string $projectName 
     * @return boolean If the project exists
     */
    public function exists($projectName)
    {
		$config = $this->getConfig('projects');

        if (! isset(self::$projects[$projectName]))
        {
            $persistentKey = Project::getPersistentKey($projectName);
            $project = Storage::load($persistentKey);
            $config = $this->getConfig('projects');

			// if it is not set in the config and exists
			if (! isset($config[$projectName]) && $project !== NULL)
			{
				$config = $this->getConfig('projects');
				$config[$projectName];
				$this->setConfig('projects', $config);
			}

			// if it is set in the config and does not exist
			if (isset($config[$projectName]) && $project === NULL)
			{
				$config = $this->getConfig('projects');
				unset($config[$projectName]);
				$this->setConfig('projects', $config);
			}

			if ($project !== NULL)
			{
				if (! $project instanceof Project)
				{
					throw new ProjectCorruptedException();
				}

				self::$projects[$projectName] = $project;
			}
        }

        return isset(self::$projects[$projectName]);
    }


    /**
     * loadDefault
     * 
     * @access public
     * @author Pablo López Torres <pablolopeztorres@gmail.com> 
     * @param string $projectName 
     * @return Project
     */
    public function loadDefault()
    {
		$projectName = $this->getConfig('default_project');
		return $this->load($projectName);
    }

    /**
     * setDefault
     * 
     * @access public
     * @author Pablo López Torres <pablolopeztorres@gmail.com> 
     * @param string $projectName 
     * @return Project
     */
    public function setDefault($projectName)
    {
        // update the configuration with this project
        $this->setConfig('default_project', $projectName);
    }


    //
    // Operations
    //
    /**
     * getListAsArray
     *
     * Returns a list of projects 
     * 
     * @access public
     * @author Pablo López Torres <pablolopeztorres@gmail.com> 
     * @return array
     */
    public function getListAsArray()
    {
        return array_keys($this->getConfig('projects'));
    }

}

/**
 * ProjectAlreadyExistsException 
 * 
 * @uses Exception
 * @author Pablo López Torres <pablolopeztorres@gmail.com> 
 */
class ProjectAlreadyExistsException extends Exception
{
}

/**
 * ProjectCorruptedException 
 * 
 * @uses Exception
 * @author Pablo López Torres <pablolopeztorres@gmail.com> 
 */
class ProjectCorruptedException extends Exception
{
}

/**
 * ProjectNotFoundException 
 * 
 * @uses Exception
 * @author Pablo López Torres <pablolopeztorres@gmail.com> 
 */
class ProjectNotFoundException extends Exception
{
}

